﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Drawing.Imaging;
using System.Threading;
using System.Security.Cryptography;

namespace ControlLibrary
{
    public partial class FormMap : Form
    {
        #region 私有类型
        /// <summary>
        /// 子块信息
        /// </summary>
        internal sealed class SubMapInfo
        {
            public Bitmap MapSub { get; private set; }
            public Point PointSub { get; private set; }
            public int Times { get; private set; }
            public SubMapInfo(Bitmap mapSub, Point pointSub, int times)
            { this.MapSub = mapSub; this.PointSub = pointSub; this.Times = times; }
        }
        #endregion

        #region 只读变量
        const string MapFile = "map\\Map.jpg";                      //原始地图文件路径
        const string SubMapPath = "map\\{0}\\Map{1}";               //地图子块文件路径
        const string SubMapFile = "map\\{0}\\Map{1}\\{2}-{3}.jpg";  //地图子块文件
        //原始地图信息文件
        //  第1行：原始地图文件MD5值
        //  第2行：原始地图宽
        //  第3行：原始地图高
        const string MapInfoFile = "map\\Map.info";
        readonly Size sizeSub = new Size(200, 200);                 //地图块大小
        #endregion

        #region 私有变量
        Size sizeMap = new Size();                                  //原始地图尺寸
        int maxTimes = 1;                                           //最大缩小率
        int times = 0;                                              //缩小率
        float mapScale = 12.658f;                                   //地图比例尺，单位：米/像素
        Point mapLocation = new Point();                            //地图左上角坐标

        string MapMD5 = "00000000000000000000000000000000";         //原始地图MD5值
        Size[] sizesSubMap;                                         //所有缩小地图大小
        //需要显示的地图子块
        Dictionary<Point, Bitmap> subMaps = new Dictionary<Point, Bitmap>();
        #endregion

        #region 公有属性
        //地图左上角坐标
        public Point MapLocation { get { return this.mapLocation; } }
        //当前地图大小
        public Size MapSizeTimes { get { return this.sizesSubMap[times]; } }
        //原始地图尺寸
        public Size MapSize { get { return this.sizeMap; } }
        //地图缩小率
        public float Times { get { return (float)(this.maxTimes - this.times) / this.maxTimes; } }
        //地图比例尺，单位：米/像素
        public float MapScale { get { return this.mapScale; } }
        #endregion

        #region 构造函数
        public FormMap()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);

            InitializeComponent();
        }
        #endregion

        #region 公有函数
        public Point MapToClient(Point point)
        {
            Point pointMap = new Point();

            point.X = point.X * (maxTimes - times) / maxTimes;
            point.Y = point.Y * (maxTimes - times) / maxTimes;

            pointMap.X = point.X + this.mapLocation.X;
            pointMap.Y = point.Y + this.mapLocation.Y;

            return pointMap;
        }
        #endregion

        #region 窗体事件
        #region 绘制地图
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
        }

        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            if (sizesSubMap == null) return;

            if (this.ClientSize.Width > sizesSubMap[times].Width)
            {
                this.mapLocation.X = (int)(this.ClientSize.Width - sizesSubMap[times].Width) / 2;
                OnMapMove();
            }
            else if (this.mapLocation.X + this.MapSizeTimes.Width < this.ClientSize.Width)
            {
                this.mapLocation.X = this.ClientSize.Width - this.MapSizeTimes.Width;
                OnMapMove();
            }

            if (this.ClientSize.Height > sizesSubMap[times].Height)
            {
                this.mapLocation.Y = (int)(this.ClientSize.Height - sizesSubMap[times].Height) / 2;
                OnMapMove();
            }
            else if (this.mapLocation.Y + this.MapSizeTimes.Height < this.ClientSize.Height)
            {
                this.mapLocation.Y = this.ClientSize.Height - this.MapSizeTimes.Height;
                OnMapMove();
            }

            ReLoadSubMap();

            this.Invalidate();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics G = e.Graphics;

            foreach (var subMap in subMaps)
            {
                G.DrawImage(subMap.Value, subMap.Key.X * sizeSub.Width + mapLocation.X, subMap.Key.Y * sizeSub.Height + mapLocation.Y, subMap.Value.Width, subMap.Value.Height);
            }

            base.OnPaint(e);
        }
        #endregion

        #region 移动地图
        bool mouseDown = false;
        Point pointMouseDown = new Point();
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            mouseDown = true;
            pointMouseDown = e.Location;
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (mouseDown)
            {
                this.mapLocation.X += e.X - pointMouseDown.X;
                this.mapLocation.Y += e.Y - pointMouseDown.Y;

                //修正坐标
                if (!ReviseMapLocation()) OnMapMove();

                pointMouseDown = e.Location;
                ReLoadSubMap();
                this.Invalidate();
                this.Update();
                //this.Refresh();
            }
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            mouseDown = false;
        }
        #endregion

        #region 缩放地图
        protected override void OnMouseWheel(MouseEventArgs e)
        {
            if (e.Delta < 0 && times < maxTimes - 1)
            {
                foreach (var item in subMaps) item.Value.Dispose();
                subMaps.Clear();
                this.mapLocation.X = e.X - (e.X - this.mapLocation.X) * (maxTimes - times - 1) / (maxTimes - times);
                this.mapLocation.Y = e.Y - (e.Y - this.mapLocation.Y) * (maxTimes - times - 1) / (maxTimes - times);
                times++;

                ReviseMapLocation();//修正坐标

                ReLoadSubMap();
                this.OnMapResize();
            }
            else if (e.Delta > 0 && times > 0)
            {
                foreach (var item in subMaps) item.Value.Dispose();
                subMaps.Clear();
                times--;
                this.mapLocation.X = e.X - (e.X - this.mapLocation.X) * (maxTimes - times) / (maxTimes - times - 1);
                this.mapLocation.Y = e.Y - (e.Y - this.mapLocation.Y) * (maxTimes - times) / (maxTimes - times - 1);

                ReviseMapLocation();//修正坐标

                ReLoadSubMap();
                this.OnMapResize();
            }
            this.Invalidate();
        }
        #endregion
        #endregion

        #region 私有函数
        /// <summary>
        /// 保存地图信息
        /// </summary>
        /// <param name="md5">原始地图MD5值</param>
        /// <param name="size">原始地图大小</param>
        /// <param name="infoPath">地图信息文件路径</param>
        void SaveMapInfo(string md5, Size size, string infoPath)
        {
            FileStream file = new FileStream(infoPath, FileMode.Create, FileAccess.Write);
            StreamWriter SW = new StreamWriter(file);
            SW.WriteLine(md5);
            SW.WriteLine(size.Width);
            SW.WriteLine(size.Height);
            SW.Close();
            file.Close();
        }

        /// <summary>
        /// 获取缩小地图尺寸
        /// </summary>
        /// <param name="sizeMap">原始地图大小</param>
        /// <param name="maxTimes">地图最大缩小率</param>
        /// <returns>缩小地图尺寸集合</returns>
        Size[] GetSubMapSize(Size sizeMap, int maxTimes)
        {
            Size[] sizes = new Size[maxTimes];
            for (int i = 0; i < maxTimes; i++)
            {
                sizes[i] = new Size((int)(sizeMap.Width * (maxTimes - i) / maxTimes), (int)(sizeMap.Height * (maxTimes - i) / maxTimes));
            }
            return sizes;
        }

        /// <summary>
        /// 重新加载地图子块
        /// </summary>
        void ReLoadSubMap()
        {
            //增加需要显示的子块
            Size countSub = new Size(this.ClientSize.Width / this.sizeSub.Width + 2, this.ClientSize.Height / this.sizeSub.Height + 2);
            Point startPoint = new Point(-this.mapLocation.X / this.sizeSub.Width, -this.mapLocation.Y / this.sizeSub.Height);
            startPoint.X = Math.Max(0, startPoint.X);
            startPoint.Y = Math.Max(0, startPoint.Y);
            for (int y = startPoint.Y; y < startPoint.Y + countSub.Height; y++)
            {
                for (int x = startPoint.X; x < startPoint.X + countSub.Width; x++)
                {
                    Point subPoint = new Point(x, y);
                    if (!subMaps.ContainsKey(subPoint) && File.Exists(string.Format(SubMapFile, this.MapMD5, times, x, y)))
                    {
                        Bitmap subMap = new Bitmap(string.Format(SubMapFile, this.MapMD5, times, x, y));
                        subMaps.Add(subPoint, subMap);
                    }
                }
            }
            //删除不需要显示的子块
            List<Point> remove = new List<Point>();
            foreach (var subMap in subMaps)
            {
                if (subMap.Key.X < startPoint.X
                    || subMap.Key.X >= startPoint.X + countSub.Width
                    || subMap.Key.Y < startPoint.Y
                    || subMap.Key.Y >= startPoint.Y + countSub.Height)
                    remove.Add(subMap.Key);
            }
            foreach (var pointSub in remove)
            {
                subMaps[pointSub].Dispose();
                subMaps.Remove(pointSub);
            }
        }

        //生成地图子块
        void CreateSubMap(Bitmap map, int times)
        {
            Size sizeMap = new Size(map.Width / sizeSub.Width, map.Height / sizeSub.Height);
            for (int y = 0; y <= sizeMap.Height; y++)
            {
                for (int x = 0; x <= sizeMap.Width; x++)
                {
                    if (File.Exists(string.Format(SubMapFile, this.MapMD5, times, x, y))) continue;
                    Size size = sizeSub;
                    if (x == sizeMap.Width) size.Width = map.Width % sizeSub.Width;
                    if (y == sizeMap.Height) size.Height = map.Height % sizeSub.Height;
                    Rectangle rectSub = new Rectangle(x * sizeSub.Width, y * sizeSub.Height, size.Width, size.Height);
                    Bitmap mapSub = map.Clone(rectSub, PixelFormat.Format24bppRgb);
                    ThreadPool.QueueUserWorkItem(new WaitCallback(SaveSubMap), new SubMapInfo(mapSub, new Point(x, y), times));
                }
            }
        }

        /// <summary>
        /// 线程函数，保存地图子块
        /// </summary>
        /// <param name="state">SubMapInfo类型变量，表示地图子块信息</param>
        void SaveSubMap(object state)
        {
            SubMapInfo info = state as SubMapInfo;
            info.MapSub.Save(string.Format(SubMapFile, this.MapMD5, info.Times, info.PointSub.X, info.PointSub.Y));
            info.MapSub.Dispose();
        }

        /// <summary>
        /// 实现对一个文件md5的读取
        /// </summary>
        /// <param name="path">文件路径</param>
        /// <returns></returns>
        string FileMD5(string path)
        {
            FileStream get_file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
            MD5CryptoServiceProvider get_md5 = new MD5CryptoServiceProvider();
            byte[] hash_byte = get_md5.ComputeHash(get_file);
            string resule = System.BitConverter.ToString(hash_byte);
            resule = resule.Replace("-", "");
            get_file.Close();
            return resule;
        }

        /// <summary>
        /// 修正地图坐标
        /// </summary>
        /// <returns>是否修改</returns>
        bool ReviseMapLocation()
        {
            bool moveX = true, moveY = true;
            if (this.ClientSize.Width > sizesSubMap[times].Width)
                this.mapLocation.X = (int)(this.ClientSize.Width - sizesSubMap[times].Width) / 2;
            else if (this.mapLocation.X > 0)
                this.mapLocation.X = 0;
            else if (this.mapLocation.X + this.MapSizeTimes.Width < this.ClientSize.Width)
                this.mapLocation.X = this.ClientSize.Width - this.MapSizeTimes.Width;
            else
                moveX = false;

            if (this.ClientSize.Height > sizesSubMap[times].Height)
                this.mapLocation.Y = (int)(this.ClientSize.Height - sizesSubMap[times].Height) / 2;
            else if (this.mapLocation.Y > 0)
                this.mapLocation.Y = 0;
            else if (this.mapLocation.Y + this.MapSizeTimes.Height < this.ClientSize.Height)
                this.mapLocation.Y = this.ClientSize.Height - this.MapSizeTimes.Height;
            else
                moveY = false;

            if (moveX || moveY) OnMapMove();

            return moveX || moveY;
        }
        #endregion

        #region 保护函数
        protected virtual void OnMapMove() { }
        protected virtual void OnMapResize() { }
        protected void LoadMap()
        {
            if (File.Exists(MapFile))
            {
                #region 检验地图文件
                Bitmap map = null;
                this.MapMD5 = FileMD5(MapFile);
                if (!File.Exists(MapInfoFile))
                {
                    map = new Bitmap(MapFile);
                    SaveMapInfo(this.MapMD5, map.Size, MapInfoFile);
                    this.sizeMap = map.Size;
                }
                else
                {
                    StreamReader SR = new StreamReader(MapInfoFile);
                    string md5 = SR.ReadLine();
                    if (md5 != MapMD5)//检验地图是否发生更改
                    {
                        map = new Bitmap(MapFile);
                        SaveMapInfo(this.MapMD5, map.Size, MapInfoFile);
                        this.sizeMap = map.Size;
                    }
                    else
                    {
                        this.sizeMap.Width = int.Parse(SR.ReadLine());
                        this.sizeMap.Height = int.Parse(SR.ReadLine());
                    }
                    SR.Close();
                }
                #endregion

                #region 创建地图子块
                this.maxTimes = Math.Max(1, Math.Min(this.sizeMap.Width / 800, this.sizeMap.Height / 600));
                this.times = maxTimes - 1;
                //缩小后的地图大小
                sizesSubMap = GetSubMapSize(this.sizeMap, this.maxTimes);

                Bitmap[] Maps = new Bitmap[maxTimes];
                for (int i = 0; i < maxTimes; i++)
                {
                    DirectoryInfo DI = new DirectoryInfo(string.Format(SubMapPath, this.MapMD5, i));
                    if (!DI.Exists)
                    {
                        if (map == null) map = new Bitmap(MapFile);
                        DI.Create();
                        Maps[i] = new Bitmap(sizesSubMap[i].Width, sizesSubMap[i].Height);
                        Graphics G = Graphics.FromImage(Maps[i]);
                        G.DrawImage(map, 0, 0, sizesSubMap[i].Width, sizesSubMap[i].Height);
                        G.Dispose();
                        CreateSubMap(Maps[i], i);
                        Maps[i].Dispose();
                    }
                }
                if (map != null) map.Dispose();
                #endregion

            }
            else MessageBox.Show("地图文件map\\Map.jpg不存在!");

            if (sizesSubMap != null)
            {
                this.mapLocation = new Point((int)(this.ClientSize.Width - sizesSubMap[times].Width) / 2, (int)(this.ClientSize.Height - sizesSubMap[times].Height) / 2);
            }

            ReLoadSubMap();
        }
        #endregion
    }
}
